package ru.evga.billboard.graph;

import java.awt.BasicStroke;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Stroke;
import java.awt.geom.Point2D;
import java.awt.geom.Point2D.Double;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;

import ru.evga.billboard.common.JSON;
import ru.evga.billboard.common.JSONObject;
import ru.evga.billboard.editor.DrawPanel;
import ru.evga.billboard.editor.behaviors.EditorContext;
import ru.evga.billboard.graph.preference.IPreferenceChangeListener;
import ru.evga.billboard.graph.preference.GraphSettings;


public class Bus
    implements INode, IPreferenceChangeListener, JSONObject
    {
    private List<Column> columns;
    private GraphSettings preference;

    public Bus(GraphSettings preference)
    {
        this.preference = preference;
        int columnCount = preference.busColumnCount.getValue();
        columns = new ArrayList<Column>(columnCount);
        for (int i = 0; i < columnCount; i++)
        {
            columns.add(new Column(this, preference.busColumnSpace.getValue() * i, preference));
        }
        preference.addChangeListener(this);
    }

    public int getIndex(Column column)
    {
        return columns.indexOf(column);
    }
    
    public Rectangle2D getBounds(EditorContext context)
    {
        Rectangle2D r = null;
        for (Column c : columns)
        {
            Rectangle2D b = c.getBounds(context);
            if (r == null)
                r = b;
            else
                r.add(b);
        }
        return r == null ? new Rectangle2D.Double() : new Rectangle2D.Double(r.getX(), r.getY(),
            r.getWidth(), r.getHeight());
    }

    public void paint(Graphics2D g2, EditorContext context)
    {
        Column prevColumn = null;
        for (Column column : columns)
        {
            if (prevColumn != null)
            {
                drawColumnToColumn(prevColumn, column, g2, context);
            }
            drawColumn(column, g2, context);
            prevColumn = column;
        }
        drawDiode(g2, context);
    }

    private void drawColumnToColumn(Column from, Column to, Graphics2D g2, EditorContext context)
    {
        DrawPanel drawPanel = context.getDrawPanel();
        int w = drawPanel.getPreferens().busHStrokeWidth.getValue();
        if (w <= 0)
        {
            return;
        }
        double zoom = drawPanel.getZoom();
        Stroke oldStroke = g2.getStroke();
        g2.setStroke(new BasicStroke(w / 10.0f));
        g2.setColor(drawPanel.getPreferens().busHColor.getValue());
        for (int i = 0; i < from.getRowCount(); i++)
        {
            Point2D.Double pointFrom = from.getCross(i).getPoint();
            Point2D.Double pointTo = to.getCross(i).getPoint();
            g2.drawLine((int)(pointFrom.x * zoom), (int)(pointFrom.y * zoom),
                (int)(pointTo.x * zoom), (int)(pointTo.y * zoom));
        }
        g2.setStroke(oldStroke);
    }

    private void drawColumn(Column c, Graphics2D g2, EditorContext context)
    {
        Stroke oldStroke = g2.getStroke();
        DrawPanel drawPanel = context.getDrawPanel();
        int w = drawPanel.getPreferens().busVStrokeWidth.getValue();
        if (w <= 0)
        {
            return;
        }
        g2.setStroke(new BasicStroke(w / 10.0f));
        double zoom = drawPanel.getZoom();
        g2.setColor(drawPanel.getPreferens().busVColor.getValue());
        for (int i = 0; i < c.getRowCount() - 1; i++)
        {
            Double pointFrom = c.getCross(i).getPoint();
            Double pointTo = c.getCross(i + 1).getPoint();
            g2.drawLine((int)(pointFrom.x * zoom), (int)(pointFrom.y * zoom),
                (int)(pointTo.x * zoom), (int)(pointTo.y * zoom));
        }
        g2.setStroke(oldStroke);
    }

    private void drawDiode(Graphics2D g2, EditorContext context)
    {
        FrameSet fs = context.getDrawPanel().getGraph().getFrameSet();
        Frame f = fs.getCurrentFrame();
        for (int i = 0; i < columns.size(); i++)
        {
            Column column = columns.get(i);
            for (int j = 0; j < column.getRowCount(); j++)
            {
                Cross d = column.getCross(j);
                boolean on = f.isOn(i, j);
                d.paint(g2, context, on);
            }
        }
    }

    public Cross findDiode(double x, double y, EditorContext context)
    {
        for (Column column : columns)
        {
            Cross d = column.findCross(x, y, context);
            if (d != null)
            {
                return d;
            }
        }
        return null;
    }

    public void findDiode(double x1, double y1, double x2, double y2, List<Cross> collector,
        EditorContext context)
    {
        for (Column column : columns)
        {
            column.findCross(x1, y1, x2, y2, collector, context);
        }
    }

    public Point getPosInBus(Cross d)
    {
        for (int i = 0; i < columns.size(); i++)
        {
            Column column = columns.get(i);
            for (int j = 0; j < column.getRowCount(); j++)
            {
                Cross diode = column.getCross(j);
                if (d == diode)
                {
                    return new Point(i, j);
                }
            }
        }
        return null;
    }

    @Override
    public void onPreferenceChanged(GraphSettings preference)
    {
        setColumnCount(preference);
        setRowCount(preference);
    }
    
    
    private void setRowCount(GraphSettings preference)
    {
        for (Column c : columns)
        {
            c.setRowCount(preference);
        }
    }

    private void setColumnCount(GraphSettings preference)
    {
        int columnCount = preference.busColumnCount.getValue();
        if (columns.size() > columnCount)
        {   
            while(columns.size() > columnCount)
            {
                columns.remove(columns.size()-1);
            }
        }
        else if (columns.size() < columnCount)
        {
            while (columns.size() < columnCount)
            {
                columns.add(new Column(this,preference.busColumnSpace.getValue() * columns.size(), preference));                
            }
        }
    }

    @Override
    public Map toJSONMap()
    {
        return Collections.singletonMap("columns", columns);
    }

    public void fromJSONMap(Map<String, Object> m)
    {
        Object[] list = (Object[])m.get("columns");
        columns.clear();
        for (Object o : list)
        {
            Column c = new Column(this, 0, preference);
            c.fromJSONMap((Map<String, Object>)o);
            columns.add(c);
        }
    }
    
    public byte[] getContentAsBytes()
    {
        return JSON.toString(this).getBytes();
    }

    public void setContentFromBytes(byte[] data)
    {
        Object parse = JSON.parse(new String(data));
        fromJSONMap((Map<String, Object>)parse);
    }

    public List<Column> getColumns()
    {
        return Collections.unmodifiableList(columns);
    }
}
